package edu.washington.ling.syntric.pipeline;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

import edu.washington.ling.syntric.config.Configuration;
import edu.washington.ling.syntric.fold.Fold;
import edu.washington.ling.syntric.fold.FoldConfiguration;
import edu.washington.ling.syntric.io.ExampleDoc;
import edu.washington.ling.syntric.io.ExampleDocReader;
import edu.washington.ling.syntric.io.Example;
import edu.washington.ling.syntric.io.ExampleDocWriter;
import edu.washington.ling.syntric.io.TreeReader;
import edu.washington.ling.syntric.io.TreeRepresentation;
import edu.washington.ling.syntric.io.TreeWriter;
import edu.washington.ling.syntric.proc.ApplyRules;
import edu.washington.ling.syntric.struct.error.PrintingExceptionHandler;
import edu.washington.ling.syntric.struct.error.UnreadableRepresentationException;
import edu.washington.ling.syntric.struct.rule.BiDSPattern;
import edu.washington.ling.syntric.struct.rule.BiPSPattern;
import edu.washington.ling.syntric.struct.rule.UniDSPattern;
import edu.washington.ling.syntric.struct.rule.UniPSPattern;
import edu.washington.ling.syntric.struct.tree.Tree;
import edu.washington.ling.syntric.util.ConvertUtils;

class ConvertThread extends Thread {
	private PrintingExceptionHandler pHandler = new PrintingExceptionHandler();
	private List<String> ids;
	private ExampleDoc exf;
	private String rulePrefix;
	private TreeWriter psw;
	private Configuration config;
	
	public ConvertThread(List<String> ids, ExampleDoc exf, String rulePrefix, 
			TreeWriter psw, Configuration config) {
		this.ids = ids;
		this.exf = exf;
		this.rulePrefix = rulePrefix;
		this.psw = psw;
		this.config = config;
	}
	private void convert() {
		Map<UniDSPattern, List<UniPSPattern>> uniRuleMap = ApplyRules.readUnaryMapping(rulePrefix + "-uni-rules.txt");
		Map<BiDSPattern, List<BiPSPattern>> biRuleMap = ApplyRules.readBinaryMapping(rulePrefix + "-bi-rules.txt");
		Map<String, String> stackingRules = ConvertUtils.getStackingRules();
		
		for (String id: ids) {
			try {
				Example exr = exf.getExampleWithID(id);
				TreeRepresentation dsRep = exr.getTreeRep("DS-Const");
				TreeReader dsr = TreeReader.newReader(dsRep.getFormat());
				
				try {
					Tree ds = dsr.readTree(dsRep);
					List<Tree> systemTrees = ApplyRules.applyRules(ds, uniRuleMap, biRuleMap, stackingRules, config);
					System.out.println();
					for (Tree systemTree: systemTrees) {
						exr.addSystemTreeRep(psw.writeTree(systemTree));
					}
				}
				catch (UnreadableRepresentationException e) {
					String errStr = String.format("Could not process representation for example '%s'", id);
					pHandler.handle(e, errStr);
				}
			}
			catch(Exception e) {
				pHandler.handle(e, "Conversion thread failed.", System.out);
				e.printStackTrace(System.out);
				System.exit(-1);
			}
		}
	}
	@Override
	public void run() {
		convert();
	}
}

public class Convert {
	private static ConvertOptions opts = new ConvertOptions();
	private static PrintingExceptionHandler pHandler = new PrintingExceptionHandler();
	
	public static List<String> getCompatibleExampleIDs(ExampleDoc exf) {
		List<String> compatExampleIDs = new LinkedList<String>();
		for (String exampleID: exf.getIDs()) {
			if (ConvertUtils.isCompatible(exf.getExampleWithID(exampleID))) {
				compatExampleIDs.add(exampleID);
			}
		}
		return compatExampleIDs;
	}
	
	public static void convert(ExampleDoc exf, String outputFilePath, String rulePrefix,
			Configuration config, FoldConfiguration foldConfig, int maxThreads) {
		
		MultithreadedProcedure mtProc = new MultithreadedProcedure(maxThreads);
		try {
			ExampleDocWriter exfw = ExampleDocWriter.newWriter(outputFilePath, "xml");
			TreeWriter psw = TreeWriter.newWriter(opts.psTreeFormat);
			List<String> exampleIDs;
			if (foldConfig == null) {
				if (opts.compatOnly) {
					exampleIDs = getCompatibleExampleIDs(exf);
				} else {
					exampleIDs = new LinkedList<String>(exf.getIDs());
				}
				mtProc.addThread(new ConvertThread(exampleIDs, exf, rulePrefix, psw, config));
			} 
			else {
				exampleIDs = new LinkedList<String>();
				for (int i = 0; i < foldConfig.numFolds(); i++) {
					Fold fold = foldConfig.getFold(i);
					List<String> testExampleIDs = fold.getTestExampleIDs();
					exampleIDs.addAll(testExampleIDs);
					String foldPrefix = String.format("%s-fold%d", rulePrefix, i);			
					mtProc.addThread(new ConvertThread(testExampleIDs, exf, foldPrefix, psw, config));
				}
			}
			mtProc.run();
			if (opts.verbose > 0 && foldConfig != null) {
				for (int i = 0; i < foldConfig.numFolds(); i++) {
					Fold fold = foldConfig.getFold(i);
					List<String> testExampleIDs = fold.getTestExampleIDs();
					System.out.println(String.format("Stats for fold %d:", i));
					ApplyRules.printPerformanceStats(exf, testExampleIDs);
				}
			}
			ApplyRules.printPerformanceStats(exf, exampleIDs);
			exf.resetIterator();
			Example ex = null;
			while((ex = exf.getNextExample()) != null) {
				exfw.writeExample(ex);
			}
			exfw.close();
			System.out.println("Conversion was successful.");
		}
		catch (Exception e) {
			String errStr = "Conversion failed";
			pHandler.handle(e, errStr, System.out);
			e.printStackTrace(System.out);
			System.exit(-1);
		}
		
	}
	
	public static void main(String[] args) {
		CmdLineParser parser = new CmdLineParser(opts);
		try {
			parser.parseArgument(args);
			Configuration config;
			if (opts.configFilePath != null) {
				XMLConfiguration xmlConfig = new XMLConfiguration(opts.configFilePath);
				config = Configuration.init(xmlConfig);
			} else {
				config = Configuration.initDefault();
			}
			ExampleDocReader exfr = ExampleDocReader.newReader(opts.inputFilePath, "xml");
			ExampleDoc exf = exfr.read();
			FoldConfiguration foldConfig = null;
			if (opts.existingFoldConfigFile != null) {
				List<String> compatExampleIDs = getCompatibleExampleIDs(exf);
				foldConfig = FoldConfiguration.init(opts.existingFoldConfigFile);
				if (!foldConfig.checkExampleIDs(compatExampleIDs)) {
					System.out.println("Error: fold configuration example IDs " +
							"do not match the input file");
					System.exit(-1);
				}
			}
			convert(exf, opts.outputFilePath, opts.rulePrefix, config, foldConfig, opts.maxThreads);
		}
		catch(CmdLineException e) {
			System.out.println(e.getMessage());
			System.out.print("Usage: Convert");
			parser.printSingleLineUsage(System.out);
			System.out.println();
			parser.printUsage(System.out);
			System.exit(-1);
		}
		catch(IOException e) {
			String errStr = String.format("Example file '%s' could not be read", opts.inputFilePath);
			pHandler.handle(e, errStr, System.out);
			e.printStackTrace(System.out);
			System.exit(-1);
		}
		catch(ConfigurationException e) {
			String errStr = String.format("Configuration file '%s' could not be read", opts.configFilePath);
			pHandler.handle(e, errStr, System.out);
			e.printStackTrace(System.out);
			System.exit(-1);
		}
		catch(NoSuchElementException e) {
			String errStr = String.format("Configuration file '%s' is incomplete", opts.configFilePath);
			pHandler.handle(e, errStr, System.out);
			e.printStackTrace(System.out);
			System.exit(-1);
		} 
		catch (IllegalArgumentException e) {
			String errStr = String.format("Configuration file '%s' has illegal values", opts.configFilePath);
			pHandler.handle(e, errStr, System.out);
			e.printStackTrace(System.out);
			System.exit(-1);
		}
	}
}
